
#include "game.h"
#include <fstream>

using namespace WtfEngine;

GameObject::GameObject(const WtfEngine::GameObject::tCreationOptions &args)
: mIsOwner(args.IsOwner), mInterest(1.0), mPriority(args.Priority), msName(args.sName) {
	if(msName.isNull()) {
		msName = StringPool::Entry("");
	};

	GameManager::GetInstance().AddObject(this);
};

void GameObject::Destroy() {
	if(Kernel::GetInstance().ProcessEvent(new ObjectDestroyEvent(this)) == Event::STOPPED) {
		throw std::logic_error("Cannot stop ObjectDestroyEvent");
	};
	GameManager::GetInstance().RemoveObject(this);
};


/**** Serialization ****/

struct GameObjectState {
	float fPriority;
};

unsigned GameObject::CalculateSerializedSize() {
	return (sizeof(GameObjectState) +
		(*msName).CalculateSerializedSize() + getTypeName().CalculateSerializedSize());
};

unsigned GameObject::Serialize(void *pBuf) {
	unsigned n = getTypeName().Serialize(pBuf);
	n += (*msName).Serialize((void*)((unsigned)pBuf + n));
	reinterpret_cast<GameObjectState *>((void*)((unsigned)pBuf + n))->fPriority = mPriority;
	return n + sizeof(GameObjectState);
};

unsigned GameObject::Deserialize(void *pBuf) {
	String s;
	unsigned n = s.Deserialize(pBuf);
	assert(s == getTypeName());
	n += s.Deserialize((void*)((unsigned)pBuf + n));
	assert(s == *msName);

	mPriority = reinterpret_cast<GameObjectState *>((void*)((unsigned)pBuf + n))->fPriority;
	return n + sizeof(GameObjectState);
};

NonCopyBuffer<>::tRef GameObject::Serialize() {
	NonCopyBuffer<>::tRef rBuf = new NonCopyBuffer<>(CalculateSerializedSize());
	Serialize(rBuf->getData());
	return rBuf;
};


/**** Game Manager ****/

GameManager::GameManager()
: Task<GameManager>(-50, "Game Manager"), Loggable<GameManager>("Game Manager") {

};

void GameManager::UpdateObject(const StringPool::Entry& sName, const StringPool::Entry& sType, void * pUpdateData) {
	GameObject::tRef rObject = LookupObject(sName);
	if(rObject.isNull()) {
		rObject = CreateObject(sName, sType, false);
	};

	rObject->Deserialize(pUpdateData);
};

GameObject::tRef GameManager::CreateObject(const StringPool::Entry& sName, const StringPool::Entry& sType, bool bOwned) {
	tObjectTypeRegistry::const_iterator i = mTypeRegistry.find(sType);
	if(i != mTypeRegistry.end()) {
		return (*i->second->Bind(bOwned)->Bind(sName))();
	} else {
		throw std::runtime_error(String("Object type not registered - ") + *sType);
	};
};


void GameManager::Run() {
	
	//
	// Send updates for the working set
	//

	WorkingSetUpdateEvent::tRef rUpdate = new WorkingSetUpdateEvent();
	for(tObjectList::const_iterator i = mWorkingSet.begin(); i != mWorkingSet.end(); i++) {

		//
		// Allocate a buffer of the appropriate size, then serialize the object into it.
		// Then add the update to the event's list
		//

		(*rUpdate)[(*i)->getName()] = (*i)->Serialize();
	};

	Kernel::GetInstance().ProcessEvent(rUpdate);
};

struct GameHeader {
	unsigned VersionId;
	unsigned NumObjects;

	GameHeader(unsigned n)
	: VersionId(0), NumObjects(n) {
	};
};

NonCopyBuffer<>::tRef GameManager::SerializeGame() {
	
	// Calculate size
	unsigned n = sizeof(GameHeader);
	for(tObjectMap::const_iterator i = mObjectMap.begin(); i != mObjectMap.end(); i++) {
		n += sizeof(unsigned);
		n += i->second->CalculateSerializedSize();
	};

	// Allocate buffer
	NonCopyBuffer<>::tRef rBuf = new NonCopyBuffer<>(n);
	
	*reinterpret_cast<GameHeader*>(rBuf->getData()) = GameHeader(mObjectMap.size());
	n = sizeof(GameHeader);

	for(tObjectMap::const_iterator i = mObjectMap.begin(); i != mObjectMap.end(); i++) {

		// Size of the object (TODO: cache the size)
		*reinterpret_cast<unsigned *>(rBuf->getData() + n) = i->second->CalculateSerializedSize();
		n += sizeof(unsigned);

		n += i->second->Serialize(rBuf->getData() + n);
	};

	return rBuf;
};

void GameManager::DeserializeGame(const NonCopyBuffer<>::tRef& rBuf, tDeserializeMode mode) {
	String sName, sType;
	unsigned n, i, c, expectedSize, actualSize;
	GameHeader* pHd = reinterpret_cast<GameHeader*>(rBuf->getData());

	for(n = sizeof(GameHeader), c = 0; c < pHd->NumObjects; c++) {
		
		// Expected size
		expectedSize = *reinterpret_cast<unsigned *>(rBuf->getData() + n);
		n += sizeof(unsigned);

		i = sType.Deserialize(rBuf->getData() + n);
		sName.Deserialize(rBuf->getData() + n + i);

		GameObject::tRef rObject = LookupObject(sName);
		if(rObject.isNull()) {
			rObject = CreateObject(sName, sType, true);
		};

		actualSize = rObject->Deserialize(rBuf->getData() + n);
		assert(actualSize == expectedSize);

		n += actualSize;
		assert(n <= rBuf->getCount());
	};
};

void GameManager::SaveGameToFile(const String& sFile) {
	using namespace std;
	ofstream file(sFile.c_str(), ios::binary);

	NonCopyBuffer<>::tRef rBuf = SerializeGame();
	file.write((const char *)rBuf->getData(), rBuf->getCount());
};

void GameManager::LoadGameFromFile(const String& sFile) {
	using namespace std;

	ifstream file(sFile.c_str(), ios::binary | ios::ate);
	ifstream::pos_type size = file.tellg();
	file.seekg(0, ios::beg);

	NonCopyBuffer<>::tRef rBuf = new NonCopyBuffer<>((unsigned int)size);
	file.read((char *)rBuf->getData(), size);

	DeserializeGame(rBuf, GAME_REPLACE);

	file.close();
};

void GameManager::RegisterType(const StringPool::Entry& sName, GameManager::tFactoryFunc f) {
	mTypeRegistry.insert(std::pair<StringPool::Entry, tFactoryFunc>(sName, f));
};
